C# 知识点 part 3(协变和逆变)

Posted by moloach on 2017-08-02

在泛型集合的接口中使用变体

协变接口允许其方法返回的派生类型多于接口中指定的派生类型。

逆变接口允许其方法接受派生类型少于接口中指定的类型的参数。

在.NET Framework 4 中,多个现有接口已变为协变和逆变接口。 其中包括 IEnumerable 和 IComparable。 这使你可将对基类型的泛型集合进行操作的那些方法重用于派生类型的集合。

有关 .NET Framework 中变体接口的列表,请参阅泛型接口中的变体 (C#)。

转换泛型集合

下例阐释了 IEnumerable 接口中的协变支持的益处。 PrintFullName 方法接受 IEnumerable 类型的集合作为参数。 但可将该方法重用于 IEnumerable 类型的集合,因为 Employee 继承 Person。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Simple hierarchy of classes.  
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

public class Employee : Person { }

class Program
{
// The method has a parameter of the IEnumerable<Person> type.
public static void PrintFullName(IEnumerable<Person> persons)
{
foreach (Person person in persons)
{
Console.WriteLine("Name: {0} {1}",
person.FirstName, person.LastName);
}
}

public static void Test()
{
IEnumerable<Employee> employees = new List<Employee>();

// You can pass IEnumerable<Employee>,
// although the method expects IEnumerable<Person>.

PrintFullName(employees);

}
}

PrintFullName 方法接受 IEnumerable 类型的集合作为参数。 但可将该方法重用于 IEnumerable 类型的集合,因为 Employee 继承 Person

比较泛型集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// Simple hierarchy of classes.  
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

public class Employee : Person { }

// The custom comparer for the Person type
// with standard implementations of Equals()
// and GetHashCode() methods.
class PersonComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
if (Object.ReferenceEquals(x, y)) return true;
if (Object.ReferenceEquals(x, null) ||
Object.ReferenceEquals(y, null))
return false;
return x.FirstName == y.FirstName && x.LastName == y.LastName;
}
public int GetHashCode(Person person)
{
if (Object.ReferenceEquals(person, null)) return 0;
int hashFirstName = person.FirstName == null
? 0 : person.FirstName.GetHashCode();
int hashLastName = person.LastName.GetHashCode();
return hashFirstName ^ hashLastName;
}
}

class Program
{

public static void Test()
{
List<Employee> employees = new List<Employee> {
new Employee() {FirstName = "Michael", LastName = "Alexander"},
new Employee() {FirstName = "Jeff", LastName = "Price"}
};

// You can pass PersonComparer,
// which implements IEqualityComparer<Person>,
// although the method expects IEqualityComparer<Employee>.

IEnumerable<Employee> noduplicates =
employees.Distinct<Employee>(new PersonComparer());

foreach (var employee in noduplicates)
Console.WriteLine(employee.FirstName + " " + employee.LastName);
}
}

PersonComparer 类实现 IComparer 接口。 但可以重用此类来比较 Employee 类型的对象序列,因为 Employee 继承 Person。

委托中的变体

NET Framework 3.5 引入了变体支持,用于在 C# 中匹配所有委托的方法签名和委托类型。 这表明不仅可以将具有匹配签名的方法分配给委托,还可以将返回多个派生类型(协变)的方法分配给委托,或者将所接受参数的派生类型(逆变)数目比委托类型指定的数目少的方法分配给委托。

这包括泛型委托和非泛型委托。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//两个类和两个委托
public class First { }
public class Second : First { }
public delegate First SampleDelegate(Second a);
public delegate R SampleGenericDelegate<A, R>(A a);

// 创建SampleDelegate 或者SampleDelegate<A,R>类型的委托时,可以将以下任意方法分配给这些委托。
// Matching signature.
public static First ASecondRFirst(Second first)
{ return new First(); }

// The return type is more derived.
public static Second ASecondRSecond(Second second)
{ return new Second(); }

// The argument type is less derived.
public static First AFirstRFirst(First first)
{ return new First(); }

// The return type is more derived
// and the argument type is less derived.
public static Second AFirstRSecond(First first)
{ return new Second(); }


// 以下代码说明了方法签名与委托之间的隐性转换。
// Assigning a method with a matching signature
// to a non-generic delegate. No conversion is necessary.
SampleDelegate dNonGeneric = ASecondRFirst;
// Assigning a method with a more derived return type
// and less derived argument type to a non-generic delegate.
// The implicit conversion is used.
SampleDelegate dNonGenericConversion = AFirstRSecond;

// Assigning a method with a matching signature to a generic delegate.
// No conversion is necessary.
SampleGenericDelegate<Second, First> dGeneric = ASecondRFirst;
// Assigning a method with a more derived return type
// and less derived argument type to a generic delegate.
// The implicit conversion is used.
SampleGenericDelegate<Second, First> dGenericConversion = AFirstRSecond;

泛型类型参数中的变体

使用委托中的变体。

向委托分配方法时,协变和逆变为匹配委托类型和方法签名提供了灵活性。 协变允许方法具有的派生返回类型多于委托中定义的类型。 逆变允许方法具有的派生参数类型少于委托类型中的类型。

协变

本示例演示如何将委托与具有返回类型的方法一起使用,这些返回类型派生自委托签名中的返回类型。 DogsHandler 返回的数据类型属于 Dogs 类型,它派生自委托中定义的 Mammals 类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Mammals{}  
class Dogs : Mammals{}

class Program
{
// Define the delegate.
public delegate Mammals HandlerMethod();

public static Mammals MammalsHandler()
{
return null;
}

public static Dogs DogsHandler()
{
return null;
}

static void Test()
{
HandlerMethod handlerMammals = MammalsHandler;

// Covariance enables this assignment.
HandlerMethod handlerDogs = DogsHandler;
}
}

逆变

本示例演示如何将委托与具有某个类型的参数的方法一起使用,这些返回类型是委托签名参数类型的基类型。 通过逆变可以使用一个事件处理程序而不是多个单独的处理程序。 例如,可以创建接受 EventArgs 输入参数的事件处理程序,并将其与将 MouseEventArgs 类型作为参数发送的 Button.MouseClick 事件一起使用,也可以将其与发送 KeyEventArgs 参数的 TextBox.KeyDown 事件一起使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Event hander that accepts a parameter of the EventArgs type.  
private void MultiHandler(object sender, System.EventArgs e)
{
label1.Text = System.DateTime.Now.ToString();
}

public Form1()
{
InitializeComponent();

// You can use a method that has an EventArgs parameter,
// although the event expects the KeyEventArgs parameter.
this.button1.KeyDown += this.MultiHandler;

// You can use the same method
// for an event that expects the MouseEventArgs parameter.
this.button1.MouseClick += this.MultiHandler;

}

对Func和Action泛型委托使用变体

演示如何使用Func 和Action 泛型委托中的协变和逆变来启用重用方法并为代码中提供更多的灵活性。

使用具有协变类型参数的委托

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//simple hierarchy of classes.
public class Person{}
public class Employee:Person{}
class Program{
static Employee FindByTitle(String title){
//this is a stub for a method that returns
//an employee that has the sepecifid title.
return new Employee();
}

static void Test(){
//Create an instance of the delegate without variance.
Func<String, Employee> findEmployee = FindByTitle;

//the delegate expects an method to return Persons,
//but you can assign it a method that returns Employee.
Func<String, Employee> findPerson = FindByTitle;

//You can also assign a delegate
//that returns a more derived type
//to a delegate that returns a less derived type.
findPerson = findEmployee;
}
}

阐释了泛型 Func 委托中的协变支持的益处。 FindByTitle 方法采用 String 类型的一个参数,并返回 Employee 类型的一个对象。 但是,可将此方法分配给 Func<String, Person> 委托,因为 Employee 继承 Person

使用具有逆变类型参数的委托

下例阐释了泛型 Action 委托中的逆变支持的益处。 AddToContacts 方法采用 Person 类型的一个参数。 但是,可将此方法分配给 Action 委托,因为 Employee 继承 Person。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Person { }  
public class Employee : Person { }
class Program
{
static void AddToContacts(Person person)
{
// This method adds a Person object
// to a contact list.
}

static void Test()
{
// Create an instance of the delegate without using variance.
Action<Person> addPersonToContacts = AddToContacts;

// The Action delegate expects
// a method that has an Employee parameter,
// but you can assign it a method that has a Person parameter
// because Employee derives from Person.
Action<Employee> addEmployeeToContacts = AddToContacts;

// You can also assign a delegate
// that accepts a less derived parameter to a delegate
// that accepts a more derived parameter.
addEmployeeToContacts = addPersonToContacts;
}
}